package panda.client.ui.widgets;

import java.util.ArrayList;
import java.util.List;

import panda.client.Cardpanda;
import panda.client.JSNI;
import panda.client.engine.Observable;
import panda.client.engine.Observer;
import panda.client.enumerations.CardScale;
import panda.client.enumerations.ZIndexLevel;
import panda.client.listeners.BinExplorerMouseHandler;
import panda.client.listeners.PandaKeyDownHandler;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;

public class BinExplorer extends Composite implements Observer {
	private VerticalPanel shell = new VerticalPanel();
	private HorizontalPanel cardColumnHolder = new HorizontalPanel();
	private Label dragMeTitle = new Label();
	private Bin bin;
	private boolean visible = true;
	public static CardTitleDimension size = CardTitleDimension.threeQuarter;

	/** holds clipped rectangle coordinates of various magic card image sizes */
	public enum CardTitleDimension {
		half(5, 6, 146, 16),
		threeQuarter(8, 10, 218, 23),
		full(10, 13, 291, 31);
		
		private CardTitleDimension(int left, int top, int width, int height) {
			this.left = left;
			this.top = top;
			this.width = width;
			this.height = height;
		}
		
		private int left, top, width, height;
		
		public int getLeft() {
			return left;
		}
		
		public int getTop() {
			return top;
		}
		
		public int getWidth() {
			return width;
		}
		
		public int getHeight() {
			return height;
		}
	}

	/*
	 * This is how BinExplorer keeps track of what cards are in the bin currently. "imgs" updates on every update()
	 * command
	 */
	private List<ImageWithCardReference> imgs = new ArrayList<ImageWithCardReference>();
	private DraggableCard selected;

	private static final int rows = 15;

	public BinExplorer(final Bin bin) {
		this.bin = bin;
		bin.addObserver(this);

		dragMeTitle = new Label("Loading...");

		BinExplorerMouseHandler handler = new BinExplorerMouseHandler(this);
		dragMeTitle.addMouseOverHandler(handler);
		dragMeTitle.addMouseMoveHandler(handler);
		dragMeTitle.addMouseOutHandler(handler);
		dragMeTitle.addMouseDownHandler(handler);
		dragMeTitle.addMouseUpHandler(handler);

		dragMeTitle.setWordWrap(false);
		DOM.setStyleAttribute(dragMeTitle.getElement(), "textAlign", "center");

		shell.add(dragMeTitle);
		shell.add(cardColumnHolder);
		cardColumnHolder.setSpacing(4);
		DOM.setStyleAttribute(shell.getElement(), "backgroundColor", "white");

		setShadedStyle(dragMeTitle.getElement());
		
		DOM.setStyleAttribute(shell.getElement(), "border", "1px solid black");
		DOM.setStyleAttribute(shell.getElement(), "zIndex", String.valueOf(ZIndexLevel.BINEXPLORER.getZIndex()));
		initWidget(shell);
		shell.addStyleName("binExShell");

		JSNI.disableSelection(getElement());

		update(bin, null);
	}
	
	public static void setShadedStyle(Element el) {
		DOM.setStyleAttribute(el, "color", "white");
		DOM.setStyleAttribute(el, "padding", "0px 4px 0px 4px");
		DOM.setStyleAttribute(el, "backgroundColor", "rgb(65, 160, 255)"); // rgb(65, 160, 255)
		DOM.setStyleAttribute(el, "backgroundImage", "url(" + NoteTip.GradientURL + ")");
		DOM.setStyleAttribute(el, "backgroundPosition", "bottom");
		
		JSNI.disableSelection(el);
	}

	public String getName() {
		return bin.getName();
	}

	public boolean isEmpty() {
		return imgs.isEmpty();
	}

	public String getOwnerName() {
		return bin.getOwnerName();
	}

	/**
	 * Hides or shows the binEx. Whenever we want to hide a BinEx, we need to set selectedBinEx to the appropriate hand
	 * bin.
	 */
	public void toggleVisibility() {
		if(visible) {
			hide();
		} else {
			show();
		}

		setVisible(visible);
	}

	public void show() {
		visible = true;
		setVisible(visible);
	}

	public void hide() {
		visible = false;
		setVisible(visible);
	}

	public DraggableCard removeSelectedCard() {
		DraggableCard removed = bin.removeCard(selected);
		return removed;
	}

	/**
	 * An immutable subclass of Image with a reference to a DraggableCard.
	 * 
	 * @author zane.tian@gmail.com
	 */
	private class ImageWithCardReference extends Image {
		private DraggableCard cardRef;

		public ImageWithCardReference(String url, DraggableCard cardRef) {
			super(url);
			this.cardRef = cardRef;
		}

		public DraggableCard getCardRef() {
			return cardRef;
		}
	}

	public void update(Observable o, Object arg) {
		Bin observableBin = (Bin) o;

		imgs.clear();
		cardColumnHolder.clear();

		VerticalPanel current = null;

		for(int i = 0; i < observableBin.getDraggableCardsSnapshot().size(); i++) {
			DraggableCard card = observableBin.getDraggableCardsSnapshot().get(i);

			String url = "uninitialized";
			switch(size) {
				case full:
					url = card.getUrl100();
					break;
				case threeQuarter:
					url = card.getUrl75();
					break;
				case half:
					url = card.getUrl50();
					break;
				default:
					throw new IllegalStateException("did not plan for size: " + size);
			}
			
			ImageWithCardReference img = new ImageWithCardReference(url, card);
			img.setVisibleRect(size.getLeft(), size.getTop(), size.getWidth(), size.getHeight());
			imgs.add(img);

			if(i % rows == 0) {
				current = new VerticalPanel();
				cardColumnHolder.add(current);
			}

			current.add(img);
		}

		for(final ImageWithCardReference img : imgs) {
			img.addStyleName("elliptical-binExplorer-element");
			img.addStyleName("transparent-binExplorer-element");

			img.addMouseOverHandler(new MouseOverHandler() {
				public void onMouseOver(MouseOverEvent event) {
					img.removeStyleName("transparent-binExplorer-element");
					selected = img.getCardRef();
					Cardpanda.clientModel.setLastMousedOverCard(img.getCardRef());
				}
			});

			img.addMouseOutHandler(new MouseOutHandler() {
				public void onMouseOut(MouseOutEvent event) {
					img.addStyleName("transparent-binExplorer-element");
				}
			});

			img.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {
					DraggableCard removed = removeSelectedCard();
					
					if(PandaKeyDownHandler.holdingCtrl) {
						removed.flipOver();
					}
					
					assert !removed.isTapped() : "This card, " + removed.getName() + ", should not be tapped inside a bin";
					Cardpanda.clientModel.addCardInPlay(removed, 40, Cardpanda.canvasHeight / 2
							- CardScale.QUARTER.getHeight() / 2);

					String name = Cardpanda.clientModel.getSessionID().getName();
					String logForMe = "You reveal " + removed.getName() + " from your " + getName() + ".";
					String logForOthers = name + " reveals " + removed.getName() + " from " + name + "'s " + getName() + ".";
					NoteTip.get().log(logForMe, logForOthers);
					
					Cardpanda.sendModelUpdateToServer(Cardpanda.clientModel.getSessionID());
				}
			});
		}

		/**
		 * This is deferred because Panda.clientModel isn't initialized during this BinEx's initialization. This BinEx's
		 * constructor is called in clientModel's constructor.
		 */
		DeferredCommand.addCommand(new Command() {
			public void execute() {
				if(imgs.size() != 0) {
					cardColumnHolder.setVisible(true);

					String dragMeTitleText = bin.getOwnerAndType() + ": " + /* cards */imgs.size() + " cards";
					String name = Cardpanda.clientModel.getSessionID().getName();
					dragMeTitle.setText(dragMeTitleText.replace(name + "'s", "Your"));
				} else {
					cardColumnHolder.setVisible(false);

					String dragMeTitleText = bin.getOwnerAndType() + " is empty";
					String name = Cardpanda.clientModel.getSessionID().getName();
					dragMeTitle.setText(dragMeTitleText.replace(name + "'s", "Your"));
				}
			}
		});
	}
}